package com.google.code.sms.service.metadata;

import com.google.code.sms.Logger;
import com.google.code.sms.io.InputStreamReaderThread;
import com.google.code.sms.service.TranscodingService;
import com.google.code.sms.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Parses meta-data from audio files using FFmpeg.
 *
 * @author Scott Ware
 */
public class AudioParser {

    private static final Logger LOG = Logger.getLogger("AudioParser");
    private static final Pattern TITLE_ID3 = Pattern.compile("^title\\s*: (.+)");
    private static final Pattern TITLE_OGG = Pattern.compile("^TITLE\\s*: (.+)");
    private static final Pattern TITLE_APE = Pattern.compile("^Title\\s*: (.+)");
    private static final Pattern ARTIST_ID3 = Pattern.compile("^artist\\s*: (.+)");
    private static final Pattern ARTIST_OGG = Pattern.compile("^ARTIST\\s*: (.+)");
    private static final Pattern ARTIST_APE = Pattern.compile("^Artist\\s*: (.+)");
    private static final Pattern ARTIST_APEv2 = Pattern.compile("^BAND\\s*: (.+)");
    private static final Pattern ALBUMARTIST_OGG = Pattern.compile("^album_artist\\s*: (.+)");
    private static final Pattern ALBUMARTIST_APE = Pattern.compile("^ALBUM ARTIST\\s*: (.+)");
    private static final Pattern ALBUM_ID3 = Pattern.compile("^album\\s*: (.+)");
    private static final Pattern ALBUM_OGG = Pattern.compile("^ALBUM\\s*: (.+)");
    private static final Pattern ALBUM_APE = Pattern.compile("^Album\\s*: (.+)");
    private static final Pattern COMMENT_ID3 = Pattern.compile("^comment\\s*: (.+)");
    private static final Pattern COMMENT_OGG = Pattern.compile("^COMMENT\\s*: (.+)");
    private static final Pattern COMMENT_APE = Pattern.compile("^Comment\\s*: (.+)");
    private static final Pattern GENRE_ID3 = Pattern.compile("^genre\\s*: (.+)");
    private static final Pattern GENRE_OGG = Pattern.compile("^GENRE\\s*: (.+)");
    private static final Pattern GENRE_APE = Pattern.compile("^Genre\\s*: (.+)");
    private static final Pattern YEAR_ID3v1 = Pattern.compile("^TYER\\s*: (\\d{4})");
    private static final Pattern YEAR_ID3v2 = Pattern.compile("^date\\s*: (\\d{4})");
    private static final Pattern YEAR_OGG = Pattern.compile("^DATE\\s*: (\\d{4})");
    private static final Pattern YEAR_APE = Pattern.compile("^Year\\s*: (\\d{4})");
    private static final Pattern DISCNUMBER_OGG = Pattern.compile("^disc\\s*: (\\d+)");
    private static final Pattern DISCNUMBER_APE = Pattern.compile("^DISCNUMBER\\s*: (\\d+)");
    private static final Pattern DISCSUBTITLE_OGG = Pattern.compile("^DISCSUBTITLE\\s*: (.+)");
    private static final Pattern TRACK_OGG = Pattern.compile("^track\\s*: (\\d+)");
    private static final Pattern TRACK_APE = Pattern.compile("^Track\\s*: (\\d+)");
    private static final Pattern BITRATE = Pattern.compile("bitrate: (\\d+) kb/s");
    private static final Pattern DURATION = Pattern.compile("Duration: (\\d+):(\\d+):(\\d+).(\\d+),");
    private static final Pattern AUDIO_PARAMETERS = Pattern.compile("Audio:\\s*(\\S+).*[,]\\s*(\\d+) Hz,\\s*([\\w\\.]+)");
    
    private TranscodingService transcodingService;

    /**
     * Parses meta data for the given music file.
     *
     *
     * @param file The music file to parse.
     * @return Meta-data for the file.
     */
    public MetaData parseMetaData(File file) {

        MetaData metaData = new MetaData();
        
        try 
        {
            File transcoder = transcodingService.getTranscoder();

            String[] command = new String[]{transcoder.getAbsolutePath(), "-i", file.getAbsolutePath()};
            Process process = Runtime.getRuntime().exec(command);
            InputStream stdout = process.getInputStream();
            InputStream stderr = process.getErrorStream();

            // Consume stdout, we're not interested in that.
            new InputStreamReaderThread(stdout, "ffmpeg", true).start();

            // Read everything from stderr.
            String[] data = StringUtil.readLines(stderr);
            
            // Begin Parsing
            for (String line : data)
            {
                Matcher matcher;
                
                //
                // Title
                //
                
                if(metaData.getTitle() == null)
                {
                    matcher = TITLE_ID3.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setTitle(String.valueOf(matcher.group(1)));
                    }

                    matcher = TITLE_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setTitle(String.valueOf(matcher.group(1)));
                    }

                    matcher = TITLE_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setTitle(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Artist
                //
                
                if(metaData.getArtist() == null)
                {
                    matcher = ARTIST_ID3.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setArtist(String.valueOf(matcher.group(1)));
                    }

                    matcher = ARTIST_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setArtist(String.valueOf(matcher.group(1)));
                    }

                    matcher = ARTIST_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setArtist(String.valueOf(matcher.group(1)));
                    }

                    matcher = ARTIST_APEv2.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setArtist(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Album Artist
                //
                
                if(metaData.getAlbumArtist() == null)
                {
                    matcher = ALBUMARTIST_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setAlbumArtist(String.valueOf(matcher.group(1)));
                    }

                    matcher = ALBUMARTIST_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setAlbumArtist(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Album
                //
                
                if(metaData.getAlbum() == null)
                {
                    matcher = ALBUM_ID3.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setAlbum(String.valueOf(matcher.group(1)));
                    }

                    matcher = ALBUM_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setAlbum(String.valueOf(matcher.group(1)));
                    }

                    matcher = ALBUM_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setAlbum(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Comment
                //
                
                if(metaData.getComment() == null)
                {
                    matcher = COMMENT_ID3.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setComment(String.valueOf(matcher.group(1)));
                    }

                    matcher = COMMENT_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setComment(String.valueOf(matcher.group(1)));
                    }

                    matcher = COMMENT_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setComment(String.valueOf(matcher.group(1)));
                    }
                }
                                     
                //
                // Genre
                //
                
                if(metaData.getGenre() == null)
                {
                    matcher = GENRE_ID3.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setGenre(String.valueOf(matcher.group(1)));
                    }

                    matcher = GENRE_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setGenre(String.valueOf(matcher.group(1)));
                    }

                    matcher = GENRE_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setGenre(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Date
                //
                
                if(metaData.getYear() == null)
                {
                    matcher = YEAR_ID3v1.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setYear(Integer.parseInt(matcher.group(1)));
                    }

                    matcher = YEAR_ID3v2.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setYear(Integer.parseInt(matcher.group(1)));
                    }

                    matcher = YEAR_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setYear(Integer.parseInt(matcher.group(1)));
                    }

                    matcher = YEAR_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setYear(Integer.parseInt(matcher.group(1)));
                    }
                }
                
                //
                // Disc Number
                //
                
                if(metaData.getDiscNumber() == null)
                {
                    matcher = DISCNUMBER_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setDiscNumber(Integer.parseInt(matcher.group(1)));
                    }

                    matcher = DISCNUMBER_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setDiscNumber(Integer.parseInt(matcher.group(1)));
                    }
                }
                  
                //
                // Disc Subtitle
                //
                
                if(metaData.getDiscSubtitle() == null)
                {
                    matcher = DISCSUBTITLE_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setDiscSubtitle(String.valueOf(matcher.group(1)));
                    }
                }
                
                //
                // Track Number
                //
                
                if(metaData.getTrackNumber() == null)
                {
                    matcher = TRACK_OGG.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setTrackNumber(Integer.parseInt(matcher.group(1)));
                    }

                    matcher = TRACK_APE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setTrackNumber(Integer.parseInt(matcher.group(1)));
                    }
                }
                
                //
                // Duration
                //
                
                if(metaData.getDuration() == null)
                {
                    matcher = DURATION.matcher(line);

                    if(matcher.find())
                    {                    
                        int hours = Integer.parseInt(matcher.group(1));
                        int minutes = Integer.parseInt(matcher.group(2));
                        int seconds = Integer.parseInt(matcher.group(3));
                        metaData.setDuration(hours * 3600 + minutes * 60 + seconds);
                    }
                }

                //
                // Bit Rate
                //
                
                if(metaData.getBitRate() == null)
                {
                    matcher = BITRATE.matcher(line);

                    if(matcher.find())
                    {
                        metaData.setBitRate(Integer.parseInt(matcher.group(1)));
                    }
                }
                
                //
                // Audio Parameters
                //
                
                if(metaData.getAudioCodec() == null || metaData.getAudioSampleRate() == null || metaData.getAudioConfiguration() == null)
                {
                    matcher = AUDIO_PARAMETERS.matcher(line);

                    if (matcher.find()) 
                    {
                        metaData.setAudioCodec(String.valueOf(matcher.group(1)));
                        metaData.setAudioSampleRate(Integer.parseInt(matcher.group(2)));
                        metaData.setAudioConfiguration(String.valueOf(matcher.group(3)));
                    }
                }
            }
            
            // Use filename for title if it has not been found from tags
            if(metaData.getTitle() == null)
            {
                metaData.setTitle(StringUtils.trim(FilenameUtils.getBaseName(file.getPath())));
            }
            
        } 
        catch (IOException | NumberFormatException x) 
        {
            LOG.warn("Error when parsing metadata in " + file, x);
        }

        return metaData;
    }
    
    //
    // Service Managers
    //
    
    public void setTranscodingService(TranscodingService transcodingService) {
        this.transcodingService = transcodingService;
    }
}